/**
 * 断言 as
 */
// 解决 联合类型只能访问共有属性或方法 的问题
// 例1
interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}
function isFish(animal: Cat | Fish): boolean {
    // animal.swim 报错
    // if (typeof animal.swim === "function") {
    //     return true
    // }
    // 使用assert断言只能通过编译，运行时仍可能出错
    if (typeof (animal as Fish).swim === "function") {
        return true
    }
    return false
}

// 例2
// 将父类断言为更具体的子类
// 注意：子类拥有父类的属性和方法，也可以被断言为父类
interface Error { // 系统提供的 
    name: string;
    message: string;
    stack?: string;
}
class ApiError extends Error {
    code: number = 0
}
class HttpError extends Error {
    statusCode: number = 404
}
function isApiError(error: Error) {
    if (typeof (error as ApiError).code === "number") {
        return true
    }
    return false
}

// 将类型断言为any
// window.foo = 1 报错
(window as any).foo = 1

// 注意：请勿使用双重断言
interface Cat1 {
    run(): void;
}
interface Fish1 {
    swim(): void;
}
function testCat(cat: Cat1) {
    return (cat as any as Fish1)
}


/**
 * 声明文件
 * 命名方式举例：jQuery.d.ts
 */
// 变量名 : (参数及类型) => 返回值
// declare var jQuery: (selector: string) => any;
// 安装：npm install @types/jquery
jQuery('#foo')


/**
 * 内置对象
 * 主要包含：Boolean Error Date RegExp 
 * Document HTMLElement Event NodeList
 */
let error1 = new Error('404 not found')
let allDiv: NodeList = document.querySelectorAll('div1')
// 引入node作为内置对象
// npm install @types/node --save-dev


/**
 * 类型别名 
 * 关键字type
 * type作用就是给类型起一个新名字，常用于联合类型
 */
type Name = string
type NameResolve = () => string
type NameOrResolve = Name | NameResolve
function getName(temp: NameOrResolve): Name {
    if (typeof temp === "string") {
        return temp
    } else {
        return temp()
    }
}
// type的交叉扩展
type DName = {
    name: string;
}
type DAge = {
    age: number
}
// type DUser = DName & { age: number }
type DUser = DName & DAge
let stu: DUser = { name: 'wang', age: 18 }


/**
 * 字面量类型
 * 限制取值只能是某几个字符串中的一个
 */
type EventNames = 'click' | 'scroll' | 'mousemove'
function handle(ele: Element | null, event: EventNames) {
    // do something
}
handle(document.getElementById('div'), 'scroll')
// 报错
// handle(document.getElementById('div'), 'contextmenu')


/**
 * 枚举
 * 双向映射
 * 枚举成员不能具有数值名
 */
enum Days { Sun, Mon, "Tue" }
console.log(Days["Sun"] === 0) //true
console.log(Days["Mon"] === 1) //true
console.log(Days.Mon === 1) //true
console.log(Days[0] === "Sun") //true
console.log(Days[1] === "Mon") //true

// 常数枚举
const enum Directions { up, down, left, right }
let arrDirections = [Directions.up, Directions.right]

// 外部枚举
declare enum Fruits {
    apple, banana, orange
}
let arrFruits = [Fruits.apple, Fruits.orange]


